package hyl.core.db;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import hyl.core.MyFun;
import hyl.core.run.MyTimer;

/**
 * 连接池的基础类型
 * 
 * @author 阿友 3798955@qq.com
 *
 */
public abstract class MyDataSource implements IDataSource {
	static final Logger Log = LoggerFactory.getLogger(MyDataSource.class);
	String s别名 = null;
	String jdbcDriver = ""; // 数据库驱动
	String dbUrl = ""; // 数据 URL
	String dbUsername = ""; // 数据库用户名
	String dbPassword = ""; // 数据库用户密码
	String s测试脚本 = ""; // 测试连接是否可用的测试表名，默认没有测试表
	/**
	 * i最少连接数 ,用于强制回收和定时检查回收时
	 */
	int i最少连接数 = 2;// 不可改
	/**
	 * 连接池中的连接用尽以后，每次自动补充的数量
	 */
	int i自动增加连接数 = 5;//
	/**
	 * 连接池最多数量,如果应用场景并发频繁多，每个连接用时长 ，那么需要的连接数就多
	 */
	int I最大连接数量 = 10;
	boolean b连接前是否需要测试有效 = true;
	/**
	 * 每次获取连接以后都要测试一下是否可用，再返回，
	 * 
	 * 没有测试的连接可能会导致运行异常，但速度快
	 */
	int i连接有效测试限时 = 2000;
	/**
	 * 若取出的连接有异常，可以间隔一定时间重新获取
	 */
	int i重试连接间隔 = 200;
	/**
	 * 若取出的连接有异常，可以间隔一定时间重新获取，且重试n次
	 */
	int i重试次数 = 5; // 异常重试5次后提醒一次相当于10s
	/**
	 * 每个连接最长使用时长 超过报异常
	 */
	int i连接超时 = 3600000; // 1小时=3600s 目前小系统的查询不会涉及到bi系统,查询一般不会超过1小时,超过就要处理了
	AtomicInteger i可用连接数 = new AtomicInteger(0);
	/**
	 * 每创建一个加1,作废连接时不减,用于设置连接的编号 统计连接池中所有连接,包含在用和不在用的连接
	 */
	AtomicInteger i在册连接数 = new AtomicInteger(0);
	/** 专门用来定时连接,检查连接，防止连接失效 */
	protected MyTimer _mtimer = null;
	List<String[]> _所有连接 = new ArrayList<>();
	int _当前连接索引 = 0;

	/*
	 * 在用连接池, 存放 正在使用的连接池,
	 * 
	 * 如果假设MyConnection 是可自动回收的连接对象,那么这个对象可以省略
	 */
	// private Queue<MyConnection> connections2 = null; // , 初始时为 null
	protected MyDataSource() {
	}

	//////////////////////////////////////// 初始化////////////////////////////////////////////
	protected MyDataSource(String name, String jdbcDriver) {
		this.s别名 = name;
		this.jdbcDriver = jdbcDriver;
	}

	protected boolean ini(String[] url) {
		return ini(url[0], url[1], url[2]);
	}

	/**
	 * 
	 * 根据配置初始化
	 * 
	 * @param jdbcDriver String JDBC 驱动类串
	 * @param dbUrl      String 数据库 URL
	 * @param dbUsername String 连接数据库用户名
	 * @param dbPassword String 连接数据库用户的密码
	 */
	protected boolean ini(String dbUrl, String dbUsername, String dbPassword) {
		this.dbUrl = dbUrl;
		this.dbUsername = dbUsername;
		this.dbPassword = dbPassword;
		// 启动时测试连接
		Connection conn = fv测试连接();
		if (conn == null)
			return false;
		// 如果这是第一次创建数据库连接，即检查数据库，获得此数据库允许支持的
		// 最大客户连接数目
		// i累计连接数.intValue() == 0表示初始化时
		// 判断最大连接数是否超过mysql 允许的最大连接数,如果超过 以mysql 的最大连接数为准
		DatabaseMetaData metaData;
		try {
			metaData = conn.getMetaData();
			int driverMaxConnections = metaData.getMaxConnections();
			// System.out.println("newConnection()::最大连接数量" + driverMaxConnections);
			if (driverMaxConnections > 0 && this.I最大连接数量 > driverMaxConnections) {
				this.I最大连接数量 = driverMaxConnections;
			}
		} catch (SQLException e) {
			Log.error("newConnection()::无法判断最大连接数是否超界::", e);
			return false;
		} finally {
			try {
				conn.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return true;
		// 数据库返回的 driverMaxConnections 若为 0 ，表示此数据库没有最大
		// 连接限制，或数据库的最大连接限制不知道
		// driverMaxConnections 为返回的一个整数，表示此数据库允许客户连接的数目
		// 如果连接池中设置的最大连接数量大于数据库允许的连接数目 , 则置连接池的最大
		// 连接数目为数据库允许的最大数目
	}

	public void addConnUrl(String dbUrl, String dbUsername, String dbPassword) {
		_所有连接.add(new String[] { dbUrl, dbUsername, dbPassword });
	}

	public String[] getConnUrl(int i) {
		return _所有连接.get(i);
	}

	public void removeConnUrl(int i) {
		_所有连接.remove(i);
	}

	public void clearConnUrl() {
		_所有连接.clear();
	}

	public void cb启动监控任务(String name) {
	}

	protected Connection fv测试连接() {
		/*
		 * int a = this.dbUrl.indexOf("//") + 2; int b = this.dbUrl.indexOf(":", a); if
		 * (a > 5) { String ip = this.dbUrl.substring(a, b); if (!MyFun.ping(ip)) {
		 * System.out.println("无法ping"+ip); return null; } }
		 */
		try {
			return DriverManager.getConnection(this.dbUrl, this.dbUsername, this.dbPassword);
			// 每次查询或操作最多用时 ，如果超过 ，连接自动关闭，会影响到后续查询
		} catch (Exception e) {
			System.out.println("DriverManager.getConnection 失效" + this.dbUrl + ", user:" + this.dbUsername + ", pass:"
					+ this.dbPassword);
			if (!_所有连接.isEmpty()) {
				int an = _所有连接.size();
				_当前连接索引++;
				if (_当前连接索引 >= an)
					_当前连接索引 = 0;
				String[] urls = _所有连接.get(_当前连接索引);
				this.dbUrl = urls[0];
				this.dbUsername = urls[1];
				this.dbPassword = urls[2];
			}
			return null;
		}
	}

	/** 一直阻塞直到返回连接为止 */
	protected Connection fv尝试连接() {
		int n = 0;
		while (true) {
			Connection conn = fv测试连接();
			if (conn == null)
				n++;
			else
				return conn;
			if (n >= i重试次数) {
				Log.error("无法连接数据库,请检查jdbc配置,网络状态,数据库服务器和数据库状态");
				n = 0;
			}
		}
	}

	public boolean testConn() {
		Connection cn = fv尝试连接();
		if (cn == null)
			return false;
		else {
			try {
				cn.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return true;
		}
	}

	/**
	 * 尝试连接数据库,如果连接不成功,就一直尝试到可以连接为止,<br>
	 * 如果连接成功, 但是 没有绑定参数,就新建一个MyConnection ,并绑定<br>
	 * 如果有绑定参数,就直接修改已有连接
	 * 
	 * @param co
	 * @return
	 */
	protected MyConnection fv绑定连接(MyConnection co) {
		Connection conn = fv尝试连接();
		// 每次查询或操作最多用时 ，如果超过 ，连接自动关闭，会影响到后续查询
		try {
			conn.setNetworkTimeout(null, i连接超时);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		if (co == null)
			co = new MyConnection(this, conn);
		else
			co.setConn(conn);// 绑定连接,更换链接
		return co;
	}

	//////////////////////////////////////// 接口函数////////////////////////////////////////////
	/**
	 * 
	 * 通过调用 getConnection() 函数返回一个可用的数据库连接 ,<br>
	 * 如果当前没有可用的数据库连接，并且更多的数据库连接不能创建 <br>
	 * （如连接池大小的限制），此函数等待一会再尝试获取。<br>
	 * 
	 * @return 返回一个可用的数据库连接对象
	 * @throws InterruptedException
	 * 
	 */
	// 核心主程序
	protected MyConnection getConnection() {
		return null;
	}

	public void fv立即归还连接(MyConnection co) {// ok
	}

	protected void bindConn(Connection conn, MyConnection co) {
	}

	///////////////////////////// 可重写函数//////////////////////////////////////
	protected void fv批量新增连接() {// ok
		// 循环创建指定数目的数据库连接
		for (int x = 0; x < this.i自动增加连接数; x++) {
			if (this.I最大连接数量 <= this.i在册连接数.intValue()) {
				return;
			}
			// 如果最大连接数已经达到是不会再创建的,会影响到断网后的重新连接
			fv新增连接(fv绑定连接(null));
		}
	}

	protected void fv发现连接异常关闭(MyConnection co) {
		// fv归还连接到连接池(fv尝试连接到数据库(co));///????
	}

	protected void fv异常关闭处理(MyConnection co) {
		// fv归还连接到连接池(fv尝试连接到数据库(co));///????
	}

	protected void fv超时连接处理(MyConnection co) {
		// fv归还连接到连接池(fv尝试连接到数据库(co));///????
	}

	/* 创建连接，增加累计连接数 */
	protected void fv新增连接(MyConnection co) {// ok
	}

	/**
	 * 取出连接,如果连接已经关闭,就替换掉
	 */
	protected MyConnection fv从连接池取出下一个连接() {
		return null;
	}

	/**
	 * 关闭连接池中所有的连接，并清空连接池。
	 * 
	 * @throws SQLException
	 */
	public synchronized void exitAll() throws SQLException {// ok
	}

	/**
	 */
	public String showInfo(String s) {// ok
		StringBuilder sb = new StringBuilder(s);
		sb.append("\t线:").append(Thread.currentThread().getId());
		sb.append("\t时:").append(MyFun.getMs());
		sb.append("\t累计:").append(i在册连接数.intValue());
		sb.append(",可用:").append(this.i可用连接数.intValue());
		sb.append(",最大:").append(this.I最大连接数量);
		String ss = sb.toString();
		Log.info(ss);
		return ss;
	}

	public int getI可用连接数() {
		return i可用连接数.intValue();
	}

	/*
	 * 如果测试表为空，试着使用此连接的 setAutoCommit() 方法<br>
	 * 
	 * 来判断连接否可用（此方法只在部分数据库可用，如果不可用 ,<br>
	 * 
	 * 抛出异常）。注意：使用测试表的方法更可靠 conn.setAutoCommit(true);
	 */
	/**
	 * 设置测试表的名字
	 * 
	 * @param testTable String 测试表的名字
	 */
	public void setS测试脚本(String testTable) {
		this.s测试脚本 = testTable;
	}

	public String getJdbcDriver() {
		return jdbcDriver;
	}

	public void setJdbcDriver(String jdbcDriver) {
		this.jdbcDriver = jdbcDriver;
	}

	public int getI最少连接数() {
		return i最少连接数;
	}

	public void setI最少连接数(int i最少连接数) {
		if (i最少连接数 > 4)
			this.i最少连接数 = i最少连接数;
	}

	public boolean isB连接前是否需要测试有效() {
		return b连接前是否需要测试有效;
	}

	public void setB连接前是否需要测试有效(boolean b连接前是否需要测试有效) {
		this.b连接前是否需要测试有效 = b连接前是否需要测试有效;
	}

	public int getI连接有效测试限时() {
		return i连接有效测试限时;
	}

	public void setI连接有效测试限时(int i连接有效测试限时) {
		if (i连接有效测试限时 == 0)
			this.b连接前是否需要测试有效 = false;
		else
			this.b连接前是否需要测试有效 = true;
		if (i连接有效测试限时 > 2000)
			this.i连接有效测试限时 = i连接有效测试限时;
	}

	public int getI自动增加连接数() {
		return i自动增加连接数;
	}

	public void setI自动增加连接数(int 自动增加连接数) {
		if (自动增加连接数 > 5)
			this.i自动增加连接数 = 自动增加连接数;
	}

	public int getI连接超时() {
		return i连接超时;
	}

	public void setI连接超时(int 连接超时) {
		if (连接超时 > 3600000)
			this.i连接超时 = 连接超时;
	}

	/**
	 * 设置连接池中最大可用的连接数量
	 * 
	 * @param 设置连接池中最大可用的连接数量值
	 */
	public void setI最大连接数量(int max) {
		if (max > 10)
			this.I最大连接数量 = max;
	}

	public int getI最大连接数量() {
		return I最大连接数量;
	}
	/**
	 * 内部使用的用于保存连接池中连接对象的类
	 * 
	 * 此类中有两个成员，一个是数据库的连接，另一个是指示此连接是否
	 * 
	 * 正在使用的标志。
	 */
}
/*
 * 心得 ： 已知的异常，内部处理，未知的不可控的，必须把异常冒出来
 */
/*
 * a. 已测试：累计连接数正常 ;
 * 
 * b. 已测试：队列跟踪连接数可以做到与实际连接数同步
 * 
 * c. 已测试：连接异常后，无法创建新的mydb
 * 
 * d. 已测试：异常转正常以后能不能重新连接：答拔掉网线测试可以重新连接
 * 
 * e. 已测试：sql语句异常，不影响conn的回收！
 * 
 * 1. 连接过期后，会怎样? 2. 手动关闭conn ,需要时会不会自动创建新的？
 */
